|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.CodeAnalysis.Collections
{
/// <summary>
/// Represents a segmented dictionary that is immutable; meaning it cannot be changed once it is created.
/// </summary>
/// <remarks>
/// <para>There are different scenarios best for <see cref="ImmutableSegmentedDictionary{TKey, TValue}"/> and others
/// best for <see cref="ImmutableDictionary{TKey, TValue}"/>.</para>
///
/// <para>In general, <see cref="ImmutableSegmentedDictionary{TKey, TValue}"/> is applicable in scenarios most like
/// the scenarios where <see cref="ImmutableArray{T}"/> is applicable, and
/// <see cref="ImmutableDictionary{TKey, TValue}"/> is applicable in scenarios most like the scenarios where
/// <see cref="ImmutableList{T}"/> is applicable.</para>
///
/// <para>The following table summarizes the performance characteristics of
/// <see cref="ImmutableSegmentedDictionary{TKey, TValue}"/>:</para>
///
/// <list type="table">
/// <item>
/// <description>Operation</description>
/// <description><see cref="ImmutableSegmentedDictionary{TKey, TValue}"/> Complexity</description>
/// <description><see cref="ImmutableDictionary{TKey, TValue}"/> Complexity</description>
/// <description>Comments</description>
/// </item>
/// <item>
/// <description>Item</description>
/// <description>O(1)</description>
/// <description>O(log n)</description>
/// <description>Directly index into the underlying segmented dictionary</description>
/// </item>
/// <item>
/// <description>Add()</description>
/// <description>O(n)</description>
/// <description>O(log n)</description>
/// <description>Requires creating a new segmented dictionary</description>
/// </item>
/// </list>
///
/// <para>This type is backed by segmented arrays to avoid using the Large Object Heap without impacting algorithmic
/// complexity.</para>
/// </remarks>
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
/// <devremarks>
/// <para>This type has a documented contract of being exactly one reference-type field in size. Our own
/// <see cref="RoslynImmutableInterlocked"/> class depends on it, as well as others externally.</para>
///
/// <para><strong>IMPORTANT NOTICE FOR MAINTAINERS AND REVIEWERS:</strong></para>
///
/// <para>This type should be thread-safe. As a struct, it cannot protect its own fields from being changed from one
/// thread while its members are executing on other threads because structs can change <em>in place</em> simply by
/// reassigning the field containing this struct. Therefore it is extremely important that <strong>⚠⚠ Every member
/// should only dereference <c>this</c> ONCE ⚠⚠</strong>. If a member needs to reference the
/// <see cref="_dictionary"/> field, that counts as a dereference of <c>this</c>. Calling other instance members
/// (properties or methods) also counts as dereferencing <c>this</c>. Any member that needs to use <c>this</c> more
/// than once must instead assign <c>this</c> to a local variable and use that for the rest of the code instead.
/// This effectively copies the one field in the struct to a local variable so that it is insulated from other
/// threads.</para>
/// </devremarks>
internal readonly partial struct ImmutableSegmentedDictionary<TKey, TValue> : IImmutableDictionary<TKey, TValue>, IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IDictionary, IEquatable<ImmutableSegmentedDictionary<TKey, TValue>>
where TKey : notnull
{
public static readonly ImmutableSegmentedDictionary<TKey, TValue> Empty = new(new SegmentedDictionary<TKey, TValue>());
private readonly SegmentedDictionary<TKey, TValue> _dictionary;
private ImmutableSegmentedDictionary(SegmentedDictionary<TKey, TValue> dictionary)
{
_dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
}
public IEqualityComparer<TKey> KeyComparer => _dictionary.Comparer;
public int Count => _dictionary.Count;
public bool IsEmpty => _dictionary.Count == 0;
public bool IsDefault => _dictionary == null;
public bool IsDefaultOrEmpty => _dictionary?.Count is null or 0;
public KeyCollection Keys => new(this);
public ValueCollection Values => new(this);
ICollection<TKey> IDictionary<TKey, TValue>.Keys => Keys;
ICollection<TValue> IDictionary<TKey, TValue>.Values => Values;
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => true;
ICollection IDictionary.Keys => Keys;
ICollection IDictionary.Values => Values;
bool IDictionary.IsReadOnly => true;
bool IDictionary.IsFixedSize => true;
object ICollection.SyncRoot => _dictionary;
bool ICollection.IsSynchronized => true;
public TValue this[TKey key] => _dictionary[key];
TValue IDictionary<TKey, TValue>.this[TKey key]
{
get => this[key];
set => throw new NotSupportedException();
}
object? IDictionary.this[object key]
{
get => ((IDictionary)_dictionary)[key];
set => throw new NotSupportedException();
}
public static bool operator ==(ImmutableSegmentedDictionary<TKey, TValue> left, ImmutableSegmentedDictionary<TKey, TValue> right)
=> left.Equals(right);
public static bool operator !=(ImmutableSegmentedDictionary<TKey, TValue> left, ImmutableSegmentedDictionary<TKey, TValue> right)
=> !left.Equals(right);
public static bool operator ==(ImmutableSegmentedDictionary<TKey, TValue>? left, ImmutableSegmentedDictionary<TKey, TValue>? right)
=> left.GetValueOrDefault().Equals(right.GetValueOrDefault());
public static bool operator !=(ImmutableSegmentedDictionary<TKey, TValue>? left, ImmutableSegmentedDictionary<TKey, TValue>? right)
=> !left.GetValueOrDefault().Equals(right.GetValueOrDefault());
public ImmutableSegmentedDictionary<TKey, TValue> Add(TKey key, TValue value)
{
var self = this;
if (self.Contains(new KeyValuePair<TKey, TValue>(key, value)))
return self;
var builder = ToValueBuilder();
builder.Add(key, value);
return builder.ToImmutable();
}
public ImmutableSegmentedDictionary<TKey, TValue> AddRange(IEnumerable<KeyValuePair<TKey, TValue>> pairs)
{
var self = this;
// Optimize the case of adding to an empty collection
if (self.IsEmpty && TryCastToImmutableSegmentedDictionary(pairs, out var other) && self.KeyComparer == other.KeyComparer)
{
return other;
}
var builder = ToValueBuilder();
builder.AddRange(pairs);
return builder.ToImmutable();
}
public ImmutableSegmentedDictionary<TKey, TValue> Clear()
{
var self = this;
if (self.IsEmpty)
{
return self;
}
return Empty.WithComparer(self.KeyComparer);
}
public bool Contains(KeyValuePair<TKey, TValue> pair)
{
return TryGetValue(pair.Key, out var value)
&& EqualityComparer<TValue>.Default.Equals(value, pair.Value);
}
public bool ContainsKey(TKey key)
=> _dictionary.ContainsKey(key);
public bool ContainsValue(TValue value)
=> _dictionary.ContainsValue(value);
public Enumerator GetEnumerator()
=> new(_dictionary, Enumerator.ReturnType.KeyValuePair);
public ImmutableSegmentedDictionary<TKey, TValue> Remove(TKey key)
{
var self = this;
if (!self._dictionary.ContainsKey(key))
return self;
var builder = ToValueBuilder();
builder.Remove(key);
return builder.ToImmutable();
}
public ImmutableSegmentedDictionary<TKey, TValue> RemoveRange(IEnumerable<TKey> keys)
{
if (keys is null)
throw new ArgumentNullException(nameof(keys));
var result = ToValueBuilder();
result.RemoveRange(keys);
return result.ToImmutable();
}
public ImmutableSegmentedDictionary<TKey, TValue> SetItem(TKey key, TValue value)
{
var self = this;
if (self.Contains(new KeyValuePair<TKey, TValue>(key, value)))
{
return self;
}
var builder = ToValueBuilder();
builder[key] = value;
return builder.ToImmutable();
}
public ImmutableSegmentedDictionary<TKey, TValue> SetItems(IEnumerable<KeyValuePair<TKey, TValue>> items)
{
if (items is null)
throw new ArgumentNullException(nameof(items));
var result = ToValueBuilder();
foreach (var item in items)
{
result[item.Key] = item.Value;
}
return result.ToImmutable();
}
public bool TryGetKey(TKey equalKey, out TKey actualKey)
{
var self = this;
foreach (var key in self.Keys)
{
if (self.KeyComparer.Equals(key, equalKey))
{
actualKey = key;
return true;
}
}
actualKey = equalKey;
return false;
}
#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
=> _dictionary.TryGetValue(key, out value);
public ImmutableSegmentedDictionary<TKey, TValue> WithComparer(IEqualityComparer<TKey>? keyComparer)
{
keyComparer ??= EqualityComparer<TKey>.Default;
var self = this;
if (self.KeyComparer == keyComparer)
{
// Don't need to reconstruct the dictionary because the key comparer is the same
return self;
}
else if (self.IsEmpty)
{
if (keyComparer == Empty.KeyComparer)
{
return Empty;
}
else
{
return new ImmutableSegmentedDictionary<TKey, TValue>(new SegmentedDictionary<TKey, TValue>(keyComparer));
}
}
else
{
return ImmutableSegmentedDictionary.CreateRange(keyComparer, self);
}
}
public Builder ToBuilder()
=> new(this);
private ValueBuilder ToValueBuilder()
=> new(this);
public override int GetHashCode()
=> _dictionary?.GetHashCode() ?? 0;
public override bool Equals(object? obj)
{
return obj is ImmutableSegmentedDictionary<TKey, TValue> other
&& Equals(other);
}
public bool Equals(ImmutableSegmentedDictionary<TKey, TValue> other)
=> _dictionary == other._dictionary;
IImmutableDictionary<TKey, TValue> IImmutableDictionary<TKey, TValue>.Clear()
=> Clear();
IImmutableDictionary<TKey, TValue> IImmutableDictionary<TKey, TValue>.Add(TKey key, TValue value)
=> Add(key, value);
IImmutableDictionary<TKey, TValue> IImmutableDictionary<TKey, TValue>.AddRange(IEnumerable<KeyValuePair<TKey, TValue>> pairs)
=> AddRange(pairs);
IImmutableDictionary<TKey, TValue> IImmutableDictionary<TKey, TValue>.SetItem(TKey key, TValue value)
=> SetItem(key, value);
IImmutableDictionary<TKey, TValue> IImmutableDictionary<TKey, TValue>.SetItems(IEnumerable<KeyValuePair<TKey, TValue>> items)
=> SetItems(items);
IImmutableDictionary<TKey, TValue> IImmutableDictionary<TKey, TValue>.RemoveRange(IEnumerable<TKey> keys)
=> RemoveRange(keys);
IImmutableDictionary<TKey, TValue> IImmutableDictionary<TKey, TValue>.Remove(TKey key) => Remove(key);
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
=> new Enumerator(_dictionary, Enumerator.ReturnType.KeyValuePair);
IDictionaryEnumerator IDictionary.GetEnumerator()
=> new Enumerator(_dictionary, Enumerator.ReturnType.DictionaryEntry);
IEnumerator IEnumerable.GetEnumerator()
=> new Enumerator(_dictionary, Enumerator.ReturnType.KeyValuePair);
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).CopyTo(array, arrayIndex);
bool IDictionary.Contains(object key)
=> ((IDictionary)_dictionary).Contains(key);
void ICollection.CopyTo(Array array, int index)
=> ((ICollection)_dictionary).CopyTo(array, index);
void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
=> throw new NotSupportedException();
bool IDictionary<TKey, TValue>.Remove(TKey key)
=> throw new NotSupportedException();
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
=> throw new NotSupportedException();
void ICollection<KeyValuePair<TKey, TValue>>.Clear()
=> throw new NotSupportedException();
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
=> throw new NotSupportedException();
void IDictionary.Add(object key, object? value)
=> throw new NotSupportedException();
void IDictionary.Clear()
=> throw new NotSupportedException();
void IDictionary.Remove(object key)
=> throw new NotSupportedException();
private static bool TryCastToImmutableSegmentedDictionary(IEnumerable<KeyValuePair<TKey, TValue>> pairs, out ImmutableSegmentedDictionary<TKey, TValue> other)
{
if (pairs is ImmutableSegmentedDictionary<TKey, TValue> dictionary)
{
other = dictionary;
return true;
}
if (pairs is ImmutableSegmentedDictionary<TKey, TValue>.Builder builder)
{
other = builder.ToImmutable();
return true;
}
other = default;
return false;
}
}
}
|